#include "Ship.h"
#include <string>
#include "PlayScenarioPanel.h"
#include "Explosion.h"
#include "equipment/EquipmentUserInfo.h"
#include "../../common/DTOShipStatus.h"

#define SHIP_DEFAULT_DAMAGE 20
#define SHIP_DEFAULT_TIEMPO_INMUNE 1000
#define SH_TIEMPO_REPARACION	2000

Ship::Ship(ShipStatus* shipStatus, Widget* parent,bool duenoInstancia, bool esAdmin ) :
	WidgetMovimiento(SHIP,shipStatus->getPosX(), shipStatus->getPosY(), std::string(
			IMAGES_DEFAULT_FOLDER + shipStatus->getImagePath()
					+ IMAGES_DEFAULT_UNPRESSED), parent,SHIP_DEFAULT_DAMAGE), WidgetSonido(SHIP_DEFAULT_SOUND_EFFECT) {

	//Informacion basica
	imagePath = shipStatus->getImagePath();
	rotationVelocity = shipStatus->getVelRotacion();
	maximumVelocity = shipStatus->getVelMax();
	propulsion = shipStatus->getPropultion();
	friction = shipStatus->getFriccion();
	bulletLifeTime = shipStatus->getDuracionBala();
	angle = shipStatus->getAngle();
	owner = shipStatus->getOwner();
	burstOnSurface = NULL;
	burstOffSurface = NULL;

	goingForward = false;
	turningLeft = false;
	turningRight = false;
	shooting = false;
	keyPressed = false;

	velX = 0;
	velY = 0;
	nextVelX = velX;
	nextVelY = velY;
	nextAngle = angle;

	duenoesinstancialocal = duenoInstancia;

	loadOtherImageFiles();
	isHealing = false;
	userEquipment = new UserEquipment(owner,parent);

	// Timer de inmunidad
	timer_inmune.start();

	//TODO mejorar esto que esta mal...
	PlayScenarioPanel* panel = (PlayScenarioPanel*)parent;

	if(esAdmin){

		if(duenoesinstancialocal)
			base = new Base(owner, 0 + BASE_DEFAULT_WIDTH/2, panel->getHeight()/ 2, 16,BASE_IMAGE_PLAYER1, parent);
		else
			base = new Base(owner, panel->getWidth() - BASE_DEFAULT_WIDTH/2, panel->getHeight() / 2, 16,BASE_IMAGE_PLAYER2, parent);

	}else{
		if(duenoesinstancialocal)
			base = new Base(owner, panel->getWidth() - BASE_DEFAULT_WIDTH/2, panel->getHeight() / 2, 16,BASE_IMAGE_PLAYER2, parent);
				else
					base = new Base(owner, 0 + BASE_DEFAULT_WIDTH/2, panel->getHeight()/ 2, 16,BASE_IMAGE_PLAYER1, parent);
	}
	((PlayScenarioPanel*)parent)->add(base);
}

void Ship::handleEvent(Event event) {

	if(drawable){
		// Local event in local ship	     OR		Remote Event in remote ship
		if ((event.getSender() == this->owner && duenoesinstancialocal)
				|| (event.getSender() == STRING_EMPTY && !duenoesinstancialocal)) {

			std::stringstream owner;
			owner << "entro al handle del ship ";
			owner << " event key : " << event.getKey();
			owner << " owner: " << this->owner;

			Logger::trace("Ship", "handleEvent", &owner);

			EventType eventType = event.getType();

			//Translate EventType
			if (eventType == UserEvent) {
				if (event.getEvent()->user.code == UserKeyPressed) {
					eventType = KeyPressed;
				} else if (event.getEvent()->user.code == UserKeyReleased) {
					eventType = KeyReleased;
				}
			}

			switch (event.getKey()) {
			case KeyUp:
				if (eventType == KeyPressed) {
					goingForward = true;
				} else if (eventType == KeyReleased) {
					goingForward = false;
				}
				keyPressed = false;
				break;
			case KeyLeft:
				if (eventType == KeyPressed) {
					turningLeft = true;
				} else if (eventType == KeyReleased) {
					turningLeft = false;
				}
				keyPressed = false;
				break;
			case KeyRight:
				if (eventType == KeyPressed) {
					turningRight = true;
				} else if (eventType == KeyReleased) {
					turningRight = false;
				}
				keyPressed = false;
				break;
			case Space:
				if ((eventType == KeyPressed) && (keyPressed == false)) {
					shooting = true;
					keyPressed = true;
				} else if ((keyPressed == true) && (eventType == KeyReleased)) {
					keyPressed = false;
				}
				break;
			case Letter_M:{
				if ((eventType == KeyPressed) && (keyPressed == false)) {
					shooting = true;
					keyPressed = true;
				} else if ((keyPressed == true) && (eventType == KeyReleased)) {
					keyPressed = false;
				}
			}
				break;
			case Letter_I:
				if ((eventType == KeyPressed) && (keyPressed == false)) {
					shooting = true;
					keyPressed = true;
				} else if ((keyPressed == true) && (eventType == KeyReleased)) {
					keyPressed = false;
				}
				break;

			default:
				break;
			}

			//Handle event del UserEquipment
			userEquipment->handleEvent(event);
		}
	}

	if(event.getEvent()->user.code == Ship_Action){
		std::string data = event.getData();
		DTOShipAction* shipAction = new DTOShipAction(data, std::string("SERVER"));

		if(shipAction->nombre_jugador == getOwner()){
			switch(shipAction->action){
				case DIE:
					die();
					break;
				case LIFE_REDUCE:
					userEquipment->reduceLife(shipAction->life_reduce,shipAction->elementType);
					break;
				case RETURN:
					resetShipInstance(shipAction->x,shipAction->y);
					break;
			}
		}
		delete shipAction;
		shipAction = NULL;
	}
}

void Ship::resolve() {
	//UserEquipment resolve
	userEquipment->resolve();

	//Reparation
	if (((PlayScenarioPanel*)parent)->isAdmin() && isDead() && isHealing){
		if (timer.get_ticks() >= SH_TIEMPO_REPARACION){
			resetShipInstance(0,0);
			isHealing = false;
		}else return;
	}

	//Timer to get seconds between frames
	if (!timer.is_started()) {
		//Se inicia controlador de tiempo por frame
		timer.start();
	}

	float secsPerFrame = (timer.get_ticks() / 1000.0);

	parcel->updateXY(nextX,nextY);

	velX = nextVelX;
	velY = nextVelY;

	angle = nextAngle;

	int posX0 = nextX;
	int posY0 = nextY;

	float deltaX = 0;
	float deltaY = 0;

	//Inicio de calculo de las nuevas posiciones
	float degreesPerSec = (1.0 / rotationVelocity) * 360.0;
	float velocity = sqrt(pow(velX, 2.0) + pow(velY, 2.0));

	if (turningLeft)
		nextAngle = ceil((fmod(angle + (degreesPerSec * secsPerFrame), 360.0)));
	else if (turningRight)
		nextAngle = ceil((fmod(angle - (degreesPerSec * secsPerFrame), 360.0)));

	if (nextAngle < 0)
		nextAngle += 360;
	else if (angle > 360)
		nextAngle -= 360;

	//Verificación si se encuentra yendo hacia adelante
	if (goingForward && velocity < maximumVelocity) {

		float angleInRadians = nextAngle * (M_PI / 180.0);

		float propulsionX = propulsion * cos(angleInRadians);
		float propulsionY = (-1.0) * propulsion * sin(angleInRadians);

		nextVelX = velX + propulsionX * secsPerFrame;
		nextVelY = velY + propulsionY * secsPerFrame;

		deltaX = fabs(velX * secsPerFrame + (1 / 2.0) * propulsionX * pow(
				secsPerFrame, 2.0));
		deltaX = floor(deltaX * 100) / 100;
		deltaX = floor(deltaX + 0.5);
		deltaY = fabs(velY * secsPerFrame + (1 / 2.0) * propulsionY * pow(
				secsPerFrame, 2.0));
		deltaY = floor(deltaY * 100) / 100;
		deltaY = floor(deltaY + 0.5);

	} else {

		if (velocity > 0) {
			float v1 = velX / velocity;
			float v2 = velY / velocity;
			float u1 = 1.0;
			float u2 = 0.0;

			float angleOposInRadians = acos((u1 * v1 + u2 * v2) / sqrt((pow(u1,
					2.0) + pow(u2, 2.0)) * (pow(v1, 2.0) + pow(v2, 2.0))));

			if (velY < 0)
				angleOposInRadians *= (-1.0);

			float frictionX = friction * cos(angleOposInRadians);
			float frictionY = friction * sin(angleOposInRadians);

			nextVelX = velX - frictionX * secsPerFrame;
			deltaX = fabs(velX * secsPerFrame + (1 / 2.0) * frictionX * pow(
					secsPerFrame, 2.0));
			deltaX = floor(deltaX * 100) / 100;
			deltaX = floor(deltaX + 0.5);

			if ((nextVelX < 0 && velX >= 0) || (nextVelX >= 0 && velX <= 0)) {
				nextVelX = 0;
				deltaX = 0;
			}

			nextVelY = velY - frictionY * secsPerFrame;
			deltaY = fabs(velY * secsPerFrame + (1 / 2.0) * frictionY * pow(
					secsPerFrame, 2.0));
			deltaY = floor(deltaY * 100) / 100;
			deltaY = floor(deltaY + 0.5);

			if ((nextVelY < 0 && velY >= 0) || (nextVelY >= 0 && velY <= 0)) {
				nextVelY = 0;
				deltaY = 0;
			}

		}

	}

	if (velX >= 0)
		nextX = posX0 + deltaX;
	else if (velX < 0)
		nextX = posX0 - deltaX;

	if (velY >= 0)
		nextY = posY0 + deltaY;
	else if (velY < 0)
		nextY = posY0 - deltaY;
	//Fin de calculo de nuevas posiciones

	//Si hay efecto de combustion se elige la superficie de combustion sino la default
	if (goingForward) {
		this->surface = burstOnSurface;
	} else
		this->surface = burstOffSurface;

	WidgetMovimiento::resolve();
	//Se inicia controlador de tiempo por frame
	timer.start();

}

void Ship::loadOtherImageFiles() {

	burstOffSurface = this->surface;
	//Retrieving combustion image file path
	std::string pathCombustionImage = IMAGES_DEFAULT_FOLDER + imagePath
			+ IMAGES_DEFAULT_PRESSED;
	burstOnSurface = SurfaceUtils::loadImage(pathCombustionImage);

}

int Ship::getBulletAngle() {
	return angle;
}

bool Ship::isShooting() {
	bool result = this->shooting;
	this->shooting = false;
	return result;
}

int Ship::getBulletLifeTime() {
	return bulletLifeTime;
}

Ship::~Ship() {
	if (burstOnSurface != NULL) {
		SDL_FreeSurface(burstOnSurface);
		burstOnSurface = NULL;
	}
	if (burstOffSurface != NULL) {
		SDL_FreeSurface(burstOffSurface);
		burstOffSurface = NULL;
	}
	surface = NULL;
	timer.stop();
}

int Ship::getFrontAmmoX() {
	SDL_Rect rectangle = parcel->getRectangle();
	float angleInRadians = angle * (M_PI / 180.0);
	return rectangle.x + (sqrt(pow(rectangle.w , 2.0) + pow(rectangle.h ,2.0)) / 2)* cos(angleInRadians);
}

int Ship::getFrontAmmoY() {
	SDL_Rect rectangle = parcel->getRectangle();
	float angleInRadians = angle * (M_PI / 180.0);
	return rectangle.y - (sqrt(pow(rectangle.w, 2.0) + pow(rectangle.h,2.0)) / 2) * sin(angleInRadians);
}

int Ship::getBackAmmoX() {
	SDL_Rect rectangle = parcel->getRectangle();
	float angleInRadians = angle * (M_PI / 180.0);
	return rectangle.x - (sqrt(pow(rectangle.w , 2.0) + pow(rectangle.h,2.0)) / 2)* cos(angleInRadians);
}

int Ship::getBackAmmoY() {
	SDL_Rect rectangle = parcel->getRectangle();
	float angleInRadians = angle * (M_PI / 180.0);
	return rectangle.y + (sqrt(pow(rectangle.w, 2.0) + pow(rectangle.h,2.0)) / 2) * sin(angleInRadians);
}

std::string Ship::getOwner() {
	return owner;
}

ShipStatus* Ship::getShipStatus() {
	if(!isDead()){
		SDL_Rect rectangle = getPosition();

		return new ShipStatus(owner, imagePath, rotationVelocity, maximumVelocity,
				propulsion, friction, bulletLifeTime, angle, rectangle.x,
				rectangle.y,userEquipment->getDTO(),drawable);
	}else{
		return NULL;
	}
}

void Ship::updateStatus(ShipStatus* toUse) {
	if(!isDead()){
		//this->drawable = toUse->getDrawable();
		this->angle = toUse->getAngle();
		this->nextX = toUse->getPosX();
		this->nextY = toUse->getPosY();
		this->nextAngle = toUse->getAngle();
		this->userEquipment->updateEquipments(toUse->getEquipment());

		parcel->updateXY(toUse->getPosX(), toUse->getPosY());
	}
}

UserEquipment *Ship::getUserEquipment() const
{
    return userEquipment;
}

void Ship::reduceLife(int value,playElement element) {
	userEquipment->reduceLife(value,element);
	if(userEquipment->getUserLife()>0){
		playSound();
	}

	//TODO verify this flow..
	((PlayScenarioPanel*)parent)->getInterface()->setDTOMensaje(getLifeReduceDTO(value,element));
}

void Ship::handleColision(WidgetMovimiento* with){
	if(!drawable) return;

	//if (((PlayScenarioPanel*)parent)->isAdmin()){
		if( with->getType() == ASTEROID || with->getType() == SHIP_BASE || with->getType() == SHIP){
			reduceLife(SHIP_DEFAULT_LIFE,with->getType());
		}else if( with->getType() == AMMO ){
			userEquipment->reduceLife(with->getDamage(),with->getType());
			// Mensaje de reduccion de vida
			//((PlayScenarioPanel*)parent)->getInterface()->setDTOMensaje(getLifeReduceDTO(with->getDamage(),with->getType()));
		}


		if (isDead() && !isHealing){
			die();
			// Mensaje de destruccion de la nave
			//((PlayScenarioPanel*)parent)->getInterface()->setDTOMensaje(getDieDTO());
		}
	//}
}

void Ship::die(){
	drawable = false;

	//flag to set the state of the ship
	isHealing = true;

	//To use in reparation time
	timer.start();

	timer_inmune.stop();

	base->reduceLives();

	SDL_Rect rectangle = parcel->getRectangle();
	((WidgetMovimientoContainer*)parent)->add(new Explosion(rectangle.x,rectangle.y,parent));
}

bool Ship::isDead(){
	return userEquipment->getUserLife() <= 0;
}

void Ship::resetShipInstance(int x, int y){
	drawable = true;

	goingForward = false;
	turningLeft = false;
	turningRight = false;
	shooting = false;
	keyPressed = false;
	velX = 0;
	velY = 0;
	nextVelX = velX;
	nextVelY = velY;
	isHealing = false;

	timer_inmune.start();

	userEquipment->initEquipment(SHIP_DEFAULT_LIFE);
	this->userEquipment->resetBonusList();

	DTOShipStatus* dtoShipStatus = this->getShipStatus()->transformInDTO();
	dtoShipStatus->posX = x;
	dtoShipStatus->posY = y;

	if(((PlayScenarioPanel*)parent)->isAdmin()){
		((PlayScenarioPanel*)parent)->resolveOverlaping(dtoShipStatus);
		((PlayScenarioPanel*)parent)->getInterface()->setDTOMensaje(getReturnDTO(dtoShipStatus->posX,dtoShipStatus->posY));
	}

	parcel->updateXY(dtoShipStatus->posX,dtoShipStatus->posY);
	nextX = dtoShipStatus->posX;
	nextY = dtoShipStatus->posY;

	delete dtoShipStatus;
}

void Ship::handleBonus(Bonus* bonus){
	userEquipment->addBonus(bonus);
}

DTOShipAction* Ship::getDieDTO(){
	return new DTOShipAction(((PlayScenarioPanel*)parent)->getOwner(),getOwner(),DIE,0,SHIP_BASE,0,0);
}

DTOShipAction* Ship::getLifeReduceDTO(int lifeReduce,playElement element){
	return new DTOShipAction(((PlayScenarioPanel*)parent)->getOwner(),getOwner(),LIFE_REDUCE,lifeReduce,element,0,0);
}

DTOShipAction* Ship::getReturnDTO(int x, int y){
	return new DTOShipAction(((PlayScenarioPanel*)parent)->getOwner(),getOwner(),RETURN,0,SHIP_BASE,x,y);
}

void Ship::showOn(SDL_Surface* surface) {

	if(duenoesinstancialocal)userEquipment->showOn(surface);

	if(drawable && !isDead()){
		//Si no es instancia local y esta equipado con invisibilidad no se dibuja el equipamiento

		//No ( no soy dueno Y es invisible)
		if(!(!duenoesinstancialocal && userEquipment->getInvisibilidad())){
			SDL_Rect rectangle = parcel->getRectangle();
			userEquipment->drawEquipment(surface,rectangle.x,rectangle.y);
			WidgetMovimiento::showOn(surface);
		}
	}

}
